15.Jenkins Kubernetes Plugin介绍与语法详解

在上一节中介绍了jenkins使用不同的安装方式配置连接kubernetes集群的方法以及简单的示例,根据kubernetes插件用途的不同,对于插件语法的介绍也会区分开来,本节主要介绍一下使用kubernetes集群动态生成Jenkins slave pod的插件Kubernetes plugin

kubernetes plugin

编写pipeline脚本集成kubernetes插件生成动态slave时,对于启动agent代理部分的代码编写根据pipeline语法类型的不同会有所差异。但无论有什么差异,有一个核心方法不变,那就是PodTemplate,顾名思义,就是pod的模板。

PodTemplate是使用kubernetes插件生成动态slave节点的核心方法。用于配置启动的agent代理(Pod)的资源对象定义。熟悉kubernetes的应该都知道,kubernetes资源对象pod的定义严格来说主要包含两部分:Pod各项配置定义和容器各项配置定义。而在Jenkins中则通过kubernetes插件的PodTemplate()方法,对这两部分进行定义,并启动代理。

下面会根据不同的语法类型分别介绍PodTemplate的配置方法。

基本语法

脚本式语法

首先看一个在脚本式pipeline中使用kubernetes插件的基本语法。

podTemplate(label: 'xxx',cloud:'',<pod_option1>,<pod_option2>,...,containers: [
    <containerTemplate(name: 'c1',<container_option1>,<container_option2>...)>,
    <containerTemplate(<name: 'c2',<container_option1>,<container_option2>...)>,
    ....
    ]){
        node('xxx'){
            stage('test'){
            ....
            }
        }
        container('c1') {
            stage('c1'){
            ....
            }
        }
        container('c2') {
            stage('c2'){
            ....
            }
        }
     }

说明

上面PodTemplate包含两部分:

第一部分为对kubernetes资源对象Pod自身的一些定义,包括pod的标签,pod的名称,使用的云(cloud)名称,挂载的volume等。

第二部分(container)为对该pod下container的定义。container是一个列表,可以包含一个或多个containerTemplate,用于详细描述container的配置参数,比如镜像地址,容器名称,镜像拉取策略等。

podTemplate()定义了一个label名称,该lable用于在node()中引用,用于生成一个唯一的pod名称。

containerTemplate()定义了该container的名称,用于作为流水线执行的环境,通过container('container_name')引用给容器。

声明式语法

相对于脚本式语法,声明式的语法就显得相对比较混乱。是因为声明式最基础的配置是将kubernetes中资源对象pod的定义的内容通过yaml方式直接放到流水线脚本中,如下所示(只展示了部分定义)。

pipeline {
    agent {
        kubernetes {
            label <label_name>
            yaml """
    kind: Pod
    metadata:
      name: <pod_name>
    spec:
      containers:
      - name: <container_name>
        image: <image_name>
        imagePullPolicy: IfNotPresent
        command:
        - xxxx
        tty: true
        volumeMounts:
          ....
      restartPolicy: Never
      volumes:
        ......
    """
       }
    }
    stages {
        stage('one') {
          steps {
            container('container_name') {
              <command1>
            }
            container('container_name') {
              <command2>
            }
          }
          steps {
          sh 'hostname'
        }
   }       
}

说明

agent{}代理配置使用kubernetes关键字通过yaml指令将yaml文件内容直接放到PodTemplate中。

在不同的stage中引用容器的方式也是通过container('container_name')语法格式。

以上便是使用脚本式与声明式流水线集成kubernetes插件动态生成jenkins slave节点的基础语法。需要说明的是,对于PodTemplage方法中Pod Template和Container Tempalte的配置,既可以在常规的Jenkins UI中配置,也可以在pipeline script中通过脚本定义配置信息。本节主要介绍在pipeline script中使用脚本的方式定义PodTemplate;在Jenkins UI中配置PodTemplate的方法将在以后介绍,并且如果会使用脚本定义PodTemplate后,在Jenkins UI中配置相对会简单一些。

参数说明

PodTemplate()既然是方法,那么肯定会涉及到参数配置,所以接下来先了解一下PodTemplate方法的参数配置,为了区分pod配置和container配置,下面会将PodTemplate()总体配置拆分为Pod Template配置和container Template配置分别进行说明。

Pod Template 配置部分参数说明- cloud :用来指定在jenkins的系统配置中设置的云名称,默认为kubernetes。在上一节中对于该设置已经做个说明,这里不在重复介绍。

name :pod名称。

namespace :pod运行的namespace(命名空间)。

label :pod的标签. 可以自己定义,也可以使用插件内置的 POD_LABEL 变量。

yaml :Pod定义的yaml表现形式,可参考kubernetes官网

yamlMergeStrategy :包含merge()override()属性。控制yaml定义是覆盖还是与从声明(或者继承)的pod模板的yaml定义合并。默认为override()

containers :Pod内容器模板的定义。

serviceAccount :Pod使用的服务账户。

nodeSelector :生成的Pod绑定到的node节点,以key:value的形式表现。

nodeUsageMode :包括NORMALEXCLUSIVE两个值,它控制Jenkins在选择到这个代理的方式是:只有指定该节点标签时使用这个节点,还是尽可能多地使用该节点。

volumes :定义的数据卷,用于pod和所有容器。

envVars:应用于所有容器的环境变量。

envVar :可以理解为在pipeline内定义的环境变量。

secretEnvVar :secret变量,其值是从Kubernetes 的secret获取的。

imagePullSecrets :一个存放私有仓库认证信息的secret,用于从私有仓库拉取镜像时对私有仓库认证。

annotations:Pod的注解。

InheritFrom:继承的一个或多个Pod模板的列表。

slaveConnectTimeout :代理pod连接jenkins的超时时间(以秒为单位_)_。

podRetention :控制保留Pod的行为,用户设置在agent执行完毕后是否保留该pod。有多个选项,可以是never()onFailure()always()default()。如果为空,则默认为在经过activeDeadlineSeconds指定的时间之后删除pod 。

activeDeadlineSeconds :如果 podRetention 参数设置为never() 或者 onFailure(),pod经过该参数设置的时间后会自动删除。

idleMinutes :允许Pod保持活动状态以供重用,直到时间达到从开始执行到经过该值设置时间为止,默认为分钟。

showRawYaml :启用或禁用原始yaml文件的输出。默认为true

runAsUser :用于设置Pod中所有容器运行的用户ID。

runAsGroup :用于设置Pod中所有容器运行的组ID。

hostNetwork :使用宿主机网络,类似docker中的network=host

workspaceVolume :用于工作空间的卷的类型。也就是kubernetes中volume的挂载类型,可以是:

emptyDirWorkspaceVolume (default)- dynamicPVC()- hostPathWorkspaceVolume()- nfsWorkspaceVolume()- persistentVolumeClaimWorkspaceVolume()

container Template配置部分参数说明

name :容器名称。- image :容器使用的镜像。- envVars:应用于容器的环境变量(补充和覆盖在pod级别设置的envvar)。- envVar :同上。- secretEnvVar :同上。- command :容器要执行的命令。- args :执行命令要传的参数。- ttyEnabled :标记容器开启tty。- livenessProbe :容器探针。- ports :容器端口。- alwaysPullImage :拉取镜像策略- runAsUser :用于运行容器的用户ID。- runAsGroup :用于运行容器的用户组ID。.

上面的参数对于脚本式和声明式pipeline都是适用的。对于声明式的语法,也可以通过在前面介绍的片段生成器生成相应的PodTemplate语法片段。

有关Pod TemplateContainer Template的更多参数可以参考kubernetes中资源对象pod的yaml定义,默认情况下,Pod中支持的参数,在pipeline中都是可以使用的。

了解了基本语法,下面通过一些基本的示例来加深一下理解。

语法示例

针对脚本式语法和声明式语法的不同,对于示例也会分开介绍。

脚本式语法

基础示例如下:

podTemplate {
    node(POD_LABEL) {
        stage('Run shell') {
            sh 'echo hello world'
        }
    }
}

说明

对于脚本式语法,node()块是被包含在PodTemplate方法里的。

该示例会启动一个kubernetes Pod,并输出hello world

node(POD_LABEL)POD_LABEL用于给pipeline的agent代理(kubernetes pod)指定一个标签,确保pod名称的唯一性,这个标签可以自定义(通过podTemplate 的label参数指定),也可以像上面示例一样,使用POD_LABEL,这个是在kubernetes plugin插件的1.17及以后版本添加的功能,用于自动生成pod 标签,标签的格式为${JOB_NAME)-${BUILD_NUMBER}-hash_number

通过上面的示例会发现,PodTemplate既没有定义Pod参数配置,也没有定义container的参数配置。在没有配置这些参数的情况下,pipeline会使用默认的PodTemplate配置,在执行上面的pipeline后,Jenkins的构建日志中会列出该Pod的yaml定义(可参考上面最初的示例),包括镜像版本,jenkins的环境变量等。由于是默认的配置,对我们编写pipeline 持续交付脚本帮助不大,我们需要自己定义Pod运行参数,所以这里就不在重复介绍默认的pod定义。有兴趣的可以执行上面的示例,结果会通过jenkins的console日志显示出来。

对于上面的基础示例,可以添加container参数,用来自定义pod内容器的启动参数,比如:

podTemplate(containers: […]) {
  node(POD_LABEL) {
    stage('Run shell') {
      container('mycontainer') {
        sh 'echo hello world'
      }
    }
  }
}

该示例中,container参数定义了启动容器用到的镜像配置模板,在不填写container参数的情况,默认的jnlp代理(container)的配置参数如下所示:

containerTemplate(name: 'jnlp', image: 'jenkins/jnlp-slave:3.35-5-alpine', args: '${computer.jnlpmac} ${computer.name}')

需要注意的是

对于${computer.jnlpmac} ${computer.name}这两个参数是jnlp-agent(该示例为jnlp-slave:3.35-5-alpine)镜像运行必须的参数,如果不写或少写一个,都会导致slave pod生成失败。

另一个要说明的点是,该contianer的名称,实际工作中我们可能需要使用自己定义的一些镜像,这些镜像如果是jnlp-agent类型(启动时通过slave agent包启动并连接Jenkins master节点)的镜像,则容器名称就必须为jnlp,否则有可能会导致pod生成失败,至于原因将在下一章节进行说明。

对于默认的jnlp代理(container)相应的声明式语法如下:

apiVersion: v1
kind: Pod
spec:
  containers:
  - name: jnlp
    image: 'jenkins/jnlp-slave:3.35-5-alpine'
    args: ['\$(JENKINS_SECRET)', '\$(JENKINS_NAME)']

通过该示例,可以知道上面脚本式语法中的两个参数是做什么用的了吧。如果还不知道,可参考jenkins的docker-jnlp-slave

默认的container配置就只包含上面的三个参数,name,image,args,除了这些参数外,还有一些经常用到的,比如用于分配伪终端的ttyEnabled参数、设置拉取镜像策略的alwaysPullImage参数、配置是否使用privileged特权模式的参数、设置容器启动内存和cpu等。

更多与容器相关的参数配置可参考如下示例:

 containerTemplate(
        name: 'mariadb',
        image: 'mariadb:10.1',
        ttyEnabled: true,
        privileged: false,
        alwaysPullImage: false,
        workingDir: '/home/jenkins/agent',
        resourceRequestCpu: '50m',
        resourceLimitCpu: '100m',
        resourceRequestMemory: '100Mi',
        resourceLimitMemory: '200Mi',
        envVars: [
            envVar(key: 'MYSQL_ALLOW_EMPTY_PASSWORD', value: 'true'),
            secretEnvVar(key: 'MYSQL_PASSWORD', secretName: 'mysql-secret', secretKey: 'password'),
            ...
        ],
        ports: [portMapping(name: 'mysql', containerPort: 3306, hostPort: 3306)]
    ),

与pod Template相关的参数配置如下:

podTemplate(cloud: 'kubernetes', label: 'my-defined', containers: [....
],
volumes: [
    emptyDirVolume(mountPath: '/etc/mount1', memory: false),
    secretVolume(mountPath: '/etc/mount2', secretName: 'my-secret'),
    configMapVolume(mountPath: '/etc/mount3', configMapName: 'my-config'),
    hostPathVolume(mountPath: '/etc/mount4', hostPath: '/mnt/my-mount'),
    nfsVolume(mountPath: '/etc/mount5', serverAddress: '127.0.0.1', serverPath: '/', readOnly: true),
    persistentVolumeClaim(mountPath: '/etc/mount6', claimName: 'myClaim', readOnly: true)
],
namesapce: default,
serviceaccount: default,
imagePullSecrets: [ 'pull-secret' ],  //用于在启动容器时从私有仓库拉取镜像,而无需在宿主机对私有仓库进行认证登录
annotations: [
    podAnnotation(key: "my-key", value: "my-value")
    ...
]) 

编写pipeline脚本时对于PodTemplate方法中的某个参数如果不知道如何定义,都可以参考上面展示的模版范例,或者通过在前面的介绍的片段生成器通过jenkins ui定义来生成语法片段。

podTemplate方法的配置除了直接使用key:value方式定义外,也可以通过以yaml(kubernetes中资源对象文件的默认后缀)文件的方式定义,比如下面示例:

podTemplate(yaml: """
apiVersion: v1
kind: Pod
metadata:
  labels:
    some-label: some-label-value
spec:
  containers:
  - name: busybox
    image: busybox
    command:
    - cat
    tty: true
"""
) {
    node(POD_LABEL) {
      container('busybox') {
        sh "hostname"
      }
    }
}

除了使用yaml关键字外,也可以使用yamlFile关键字,用于指定一个yaml文件,该文件通常与jenkinsfile文件一起存在于源码仓库中。通过从源码仓库中拉取Jenkinsfile文件和该参数指定的文件,下载后会自动执行。这种方式需要改变pipeline job中脚本定义的类型,默认使用Pipeline script,使用yamlFile的方式就该使用Pipeline script from SCM(前面章节有介绍)的方式了。

运行多个容器

对于不同的代码要是使用不同的构建环境怎么办?在container参数中可以定义多个containerTemplate来满足此需求。也就是在一个pod中运行多个容器,熟悉kubernetes的应该都知道,同一个pod内的容器共享主机名、网络、vloume等信息,所以在pod内运行一个或者多个容器,它们之间并没有什么区别。如下示例:

podTemplate(label:'test', containers: [
    containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
    containerTemplate(name: 'golang', image: 'golang:1.8.0', ttyEnabled: true, command: 'cat')
  ]) {

    node('test') {
        stage('Get a Maven project') {
            git 'https://github.com/jenkinsci/kubernetes-plugin.git'
            container('maven') {
                stage('Build a Maven project') {
                    sh 'echo build maven'
                }
            }
        }

        stage('Get a Golang project') {
            git url: 'https://github.com/hashicorp/terraform.git'
            container('golang') {
                stage('Build a Go project') {
                    sh 'echo build go'
                }
            }
        }

    }
}

说明:

该示例定义多个容器,在不同的stage通过定义的容器的名称来使用该容器作为流水线执行的环境。

声明式语法

在声明式语法中使用PodTemplate方法与在脚本式语法中使用该方法的方式基本一致。主要区别在于agent的定义方式上。

首先看一个在声明式脚本中使用PodTemplate的基础示例:

pipeline {
  agent {
    kubernetes {
      yaml """
apiVersion: v1
kind: Pod
metadata:
  labels:
    some-label: some-label-value
spec:
  containers:
  - name: maven
    image: maven:alpine
    command:
    - cat
    tty: true
  - name: busybox
    image: busybox
    command:
    - cat
    tty: true
"""
    }
  }
  stages {
    stage('Run maven') {
      steps {
        container('maven') {
          sh 'mvn -version'
        }
        container('busybox') {
          sh '/bin/busybox'
        }
      }
    }
  }
}

说明:

使用声明式脚本,agent的type必须是”any, docker, dockerfile, kubernetes, label, none”中的一种,本节为kubernetes插件的使用,所以agent的type为kubernetes。

kubernetes使用yaml方式定义Pod和container模板配置信息,在stage阶段使用container指令指定运行的容器提供流水线执行的环境。

该pod定义未指定pod工作的namespace(命名空间),这里需要说明一下的是,如果yaml定义没有指定namespace,则默认使用在jenkins系统配置中添加kubernetes云时指定的namespace,如果这里也没设定namespace的名称,则默认使用default namespace;如果都指定了,则默认使用在pipeline脚本中定义的namespace。

上面示例 stage阶段对容器的引用方式与脚本式流水线引用容器的方式一样。

有关yaml更全面的定义,可以参考kubernetes中对资源对象pod的定义,参考kubernetes官网

除了可以在脚本式语法中使用yamlFile指令外,在声明式语法中也可以使用yamlFile指令。如下示例:

pipeline {
  agent {
    kubernetes {
      yamlFile 'KubernetesPod.yaml'
    }
  }
  stages {
      ...
  }
}

说明

示例中指定KubernetesPod.yaml文件与jenkinsfile文件放在同一源码仓库的同级目录下,配置job pipeline时使用Pipeline script from SCM。如果指定的kubernetesPod.yaml文件与Jenkinsfile文件没在同一级目录,指定该文件时需要加上相对路径。

使用多个容器

默认情况下,声明式脚本的模板不会从父模板继承。所以即便在定义了全局agent代理的情况下,也可以在指定的stage单独定义agent。

如下示例,定义了全局agent,也可以在stage定义agent,互不影响。

pipeline {
  agent {
    kubernetes {
      label 'parent-pod'
      yaml """
spec:
  containers:
  - name: golang
    image: golang:1.6.3-alpine
    command:
    - cat 
    tty: true
"""
    }
  }
  stages {
    stage('Run maven') {
        agent {
            kubernetes {
                label 'nested-pod'
                yaml """
spec:
  containers:
  - name: maven
    image: maven:3.3.9-jdk-8-alpine
    command:
    - cat 
    tty: true
"""
            }
        }
      steps {
        container('maven') {
          sh 'mvn -version'
        }
      }
    }
    stage('run go'){
        steps {
        container('golang') {
          sh 'go --version'
        }
      }
    }
  }
}

以上就是使用kubernetes插件动态生成jenkins slave节点时用到的一些语法示例。在学会了前面jenkins与docker pipeline插件集成的语法及示例后,对于本章节的内容学习应该相对简单很多。

有关在jenkins ui中定义PodTemplate以及自定义image镜像等内容会在下一章以实际案例的方式说明。